/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "drawing_canvas.h"

#include "src/utils/SkUTF.h"

#include "drawing_canvas_utils.h"
#include "image_pixel_map_mdk.h"
#include "native_pixel_map.h"
#include "native_pixel_map_manager.h"
#include "pixelmap_native_impl.h"
#include "recording/recording_canvas.h"
#include "utils/log.h"

using namespace Keels;
using namespace Rosen;
using namespace Drawing;

static Canvas* CastToCanvas(Keels_Drawing_Canvas* cCanvas)
{
    return reinterpret_cast<Canvas*>(cCanvas);
}

static const Path& CastToPath(const Keels_Drawing_Path& cPath)
{
    return reinterpret_cast<const Path&>(cPath);
}

static const Brush& CastToBrush(const Keels_Drawing_Brush& cBrush)
{
    return reinterpret_cast<const Brush&>(cBrush);
}

static const Pen& CastToPen(const Keels_Drawing_Pen& cPen)
{
    return reinterpret_cast<const Pen&>(cPen);
}

static const Keels::Rosen::Drawing::Region& CastToRegion(const Keels_Drawing_Region& cRegion)
{
    return reinterpret_cast<const Keels::Rosen::Drawing::Region&>(cRegion);
}

static const Bitmap& CastToBitmap(const Keels_Drawing_Bitmap& cBitmap)
{
    return reinterpret_cast<const Bitmap&>(cBitmap);
}

static const Drawing::Rect& CastToRect(const Keels_Drawing_Rect& cRect)
{
    return reinterpret_cast<const Drawing::Rect&>(cRect);
}

static const Point& CastToPoint(const Keels_Drawing_Point& cPoint)
{
    return reinterpret_cast<const Point&>(cPoint);
}

static const Point& CastToPoint(const Keels_Drawing_Point2D& cPoint)
{
    return reinterpret_cast<const Point&>(cPoint);
}

static const Point3& CastToPoint3(Keels_Drawing_Point3D& cPoint3)
{
    return reinterpret_cast<const Point3&>(cPoint3);
}

static const RoundRect& CastToRoundRect(const Keels_Drawing_RoundRect& cRoundRect)
{
    return reinterpret_cast<const RoundRect&>(cRoundRect);
}

static const TextBlob* CastToTextBlob(const Keels_Drawing_TextBlob* cTextBlob)
{
    return reinterpret_cast<const TextBlob*>(cTextBlob);
}

static const Matrix& CastToMatrix(const Keels_Drawing_Matrix& cMatrix)
{
    return reinterpret_cast<const Matrix&>(cMatrix);
}

static const Image& CastToImage(const Keels_Drawing_Image& cImage)
{
    return reinterpret_cast<const Image&>(cImage);
}

static const SamplingOptions& CastToSamplingOptions(const Keels_Drawing_SamplingOptions& cSamplingOptions)
{
    return reinterpret_cast<const SamplingOptions&>(cSamplingOptions);
}

static const Font& CastToFont(const Keels_Drawing_Font& cFont)
{
    return reinterpret_cast<const Font&>(cFont);
}

Keels_Drawing_Canvas* Keels_Drawing_CanvasCreate()
{
    return (Keels_Drawing_Canvas*)new Canvas;
}

void Keels_Drawing_CanvasDestroy(Keels_Drawing_Canvas* cCanvas)
{
    if (!cCanvas) {
        return;
    }
    delete CastToCanvas(cCanvas);
}

void Keels_Drawing_CanvasBind(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Bitmap* cBitmap)
{
    if (cBitmap == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Bind(CastToBitmap(*cBitmap));
}

void Keels_Drawing_CanvasAttachPen(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Pen* cPen)
{
    if (cPen == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->AttachPen(CastToPen(*cPen));
}

void Keels_Drawing_CanvasDetachPen(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DetachPen();
}

void Keels_Drawing_CanvasAttachBrush(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Brush* cBrush)
{
    if (cBrush == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->AttachBrush(CastToBrush(*cBrush));
}

void Keels_Drawing_CanvasDetachBrush(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DetachBrush();
}

void Keels_Drawing_CanvasSave(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Save();
}

void Keels_Drawing_CanvasSaveLayer(
    Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Rect* cRect, const Keels_Drawing_Brush* cBrush)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    SaveLayerOps slr =
        SaveLayerOps(reinterpret_cast<const Drawing::Rect*>(cRect), reinterpret_cast<const Drawing::Brush*>(cBrush));
    canvas->SaveLayer(slr);
}

void Keels_Drawing_CanvasRestore(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Restore();
}

uint32_t Keels_Drawing_CanvasGetSaveCount(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return 0;
    }
    return canvas->GetSaveCount();
}

void Keels_Drawing_CanvasRestoreToCount(Keels_Drawing_Canvas* cCanvas, uint32_t saveCount)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->RestoreToCount(saveCount);
}

void Keels_Drawing_CanvasDrawLine(Keels_Drawing_Canvas* cCanvas, float x1, float y1, float x2, float y2)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Point startPt(x1, y1);
    Point endPt(x2, y2);
    canvas->DrawLine(startPt, endPt);
}

void Keels_Drawing_CanvasDrawPath(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Path* cPath)
{
    if (cPath == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawPath(CastToPath(*cPath));
}

void Keels_Drawing_CanvasDrawPoints(
    Keels_Drawing_Canvas* cCanvas, Keels_Drawing_PointMode mode, uint32_t count, const Keels_Drawing_Point2D* pts)
{
    if (pts == nullptr || count == 0) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    if (mode < KEELS_POINT_MODE_POINTS || mode > KEELS_POINT_MODE_POLYGON) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    const Point* points = reinterpret_cast<const Point*>(pts);
    canvas->DrawPoints(static_cast<PointMode>(mode), count, points);
}

void Keels_Drawing_CanvasDrawVertices(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_VertexMode vertexMode, int32_t vertexCount,
    const Keels_Drawing_Point2D* positions, const Keels_Drawing_Point2D* texs, const uint32_t* colors, int32_t indexCount,
    const uint16_t* indices, Keels_Drawing_BlendMode mode)
{
    // 3 means the minimum number of vertices required for a triangle
    if (positions == nullptr || vertexCount < 3 || (indexCount < 3 && indexCount != 0)) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    if (vertexMode < VERTEX_MODE_TRIANGLES || vertexMode > VERTEX_MODE_TRIANGLE_FAN) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }

    if (mode < KEELS_BLEND_MODE_CLEAR || mode > KEELS_BLEND_MODE_LUMINOSITY) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }

    Point* positionsPoint = new (std::nothrow) Point[vertexCount];
    if (positionsPoint == nullptr) {
        LOGE("Keels_Drawing_CanvasDrawVertices: new position point failed.");
        return;
    }
    for (int32_t i = 0; i < vertexCount; ++i) {
        positionsPoint[i] = CastToPoint(positions[i]);
    }

    Point* texsPoint = nullptr;
    if (texs != nullptr) {
        texsPoint = new (std::nothrow) Point[vertexCount];
        if (texsPoint == nullptr) {
            delete[] positionsPoint;
            LOGE("Keels_Drawing_CanvasDrawVertices: new texs point failed.");
            return;
        }
        for (int32_t i = 0; i < vertexCount; i++) {
            texsPoint[i] = CastToPoint(texs[i]);
        }
    }

    Vertices* vertices = new Vertices();
    bool result = vertices->MakeCopy(static_cast<VertexMode>(vertexMode), vertexCount, positionsPoint, texsPoint,
        colors, indices ? indexCount : 0, indices);
    if (result) {
        canvas->DrawVertices(*vertices, static_cast<BlendMode>(mode));
    }
    delete vertices;
    delete[] positionsPoint;
    delete[] texsPoint;
}

void Keels_Drawing_CanvasDrawBackground(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Brush* cBrush)
{
    if (cBrush == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawBackground(CastToBrush(*cBrush));
}

void Keels_Drawing_CanvasDrawRegion(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Region* cRegion)
{
    if (cRegion == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawRegion(CastToRegion(*cRegion));
}

void Keels_Drawing_CanvasDrawBitmap(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Bitmap* cBitmap, float left, float top)
{
    if (cBitmap == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawBitmap(CastToBitmap(*cBitmap), left, top);
}

void Keels_Drawing_CanvasDrawPixelMapRect(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_PixelMap* pixelMap,
    const Keels_Drawing_Rect* src, const Keels_Drawing_Rect* dst, const Keels_Drawing_SamplingOptions* cSampingOptions)
{
#ifdef OHOS_PLATFORM
    std::shared_ptr<Media::PixelMap> p = nullptr;
    switch (NativePixelMapManager::GetInstance().GetNativePixelMapType(pixelMap)) {
        case NativePixelMapType::OBJECT_FROM_C:
            if (pixelMap) {
                p = reinterpret_cast<Keels_PixelmapNative*>(pixelMap)->GetInnerPixelmap();
            }
            break;
        case NativePixelMapType::OBJECT_FROM_JS:
            p = Media::PixelMapNative_GetPixelMap(reinterpret_cast<Keels_NativePixelMap_*>(pixelMap));
            break;
        default:
            break;
    }
    DrawingCanvasUtils::DrawPixelMapRect(CastToCanvas(cCanvas), p, reinterpret_cast<const Drawing::Rect*>(src),
        reinterpret_cast<const Drawing::Rect*>(dst),
        reinterpret_cast<const Drawing::SamplingOptions*>(cSampingOptions));
#endif
}

void Keels_Drawing_CanvasDrawBitmapRect(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Bitmap* cBitmap,
    const Keels_Drawing_Rect* src, const Keels_Drawing_Rect* dst, const Keels_Drawing_SamplingOptions* cSampling)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cBitmap == nullptr || dst == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    const Bitmap& bitmap = CastToBitmap(*cBitmap);
    auto image = bitmap.MakeImage();
    if (image == nullptr) {
        return;
    }
    if (src == nullptr) {
        canvas->DrawImageRect(
            *image, CastToRect(*dst), cSampling ? CastToSamplingOptions(*cSampling) : Drawing::SamplingOptions());
    } else {
        canvas->DrawImageRect(*image, CastToRect(*src), CastToRect(*dst),
            cSampling ? CastToSamplingOptions(*cSampling) : Drawing::SamplingOptions());
    }
}

void Keels_Drawing_CanvasDrawRect(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Rect* cRect)
{
    if (cRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawRect(CastToRect(*cRect));
}

void Keels_Drawing_CanvasDrawCircle(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Point* cPoint, float radius)
{
    if (cPoint == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    if (radius <= 0) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawCircle(CastToPoint(*cPoint), radius);
}

void Keels_Drawing_CanvasDrawOval(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Rect* cRect)
{
    if (cRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawOval(CastToRect(*cRect));
}

void Keels_Drawing_CanvasDrawArc(
    Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Rect* cRect, float startAngle, float sweepAngle)
{
    if (cRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawArc(CastToRect(*cRect), startAngle, sweepAngle);
}

void Keels_Drawing_CanvasDrawRoundRect(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_RoundRect* cRoundRect)
{
    if (cRoundRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawRoundRect(CastToRoundRect(*cRoundRect));
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasDrawSingleCharacter(
    Keels_Drawing_Canvas* cCanvas, const char* str, const Keels_Drawing_Font* cFont, float x, float y)
{
    if (str == nullptr || cFont == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    size_t len = strlen(str);
    if (len == 0) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    const char* currentStr = str;
    int32_t unicode = SkUTF::NextUTF8(&currentStr, currentStr + len);
    canvas->DrawSingleCharacter(unicode, CastToFont(*cFont), x, y);
    return KEELS_DRAWING_SUCCESS;
}

void Keels_Drawing_CanvasDrawTextBlob(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_TextBlob* cTextBlob, float x, float y)
{
    if (cTextBlob == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawTextBlob(CastToTextBlob(cTextBlob), x, y);
}

void Keels_Drawing_CanvasClipRect(
    Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Rect* cRect, Keels_Drawing_CanvasClipOp cClipOp, bool doAntiAlias)
{
    if (cRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    if (cClipOp < DIFFERENCE || cClipOp > INTERSECT) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    canvas->ClipRect(CastToRect(*cRect), static_cast<ClipOp>(cClipOp), doAntiAlias);
}

void Keels_Drawing_CanvasClipRoundRect(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_RoundRect* cRoundRect,
    Keels_Drawing_CanvasClipOp cClipOp, bool doAntiAlias)
{
    if (cRoundRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    if (cClipOp < DIFFERENCE || cClipOp > INTERSECT) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    canvas->ClipRoundRect(CastToRoundRect(*cRoundRect), static_cast<ClipOp>(cClipOp), doAntiAlias);
}

void Keels_Drawing_CanvasClipPath(
    Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Path* cPath, Keels_Drawing_CanvasClipOp cClipOp, bool doAntiAlias)
{
    if (cPath == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }

    if (cClipOp < DIFFERENCE || cClipOp > INTERSECT) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    canvas->ClipPath(CastToPath(*cPath), static_cast<ClipOp>(cClipOp), doAntiAlias);
}

void Keels_Drawing_CanvasRotate(Keels_Drawing_Canvas* cCanvas, float degrees, float px, float py)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Rotate(degrees, px, py);
}

void Keels_Drawing_CanvasTranslate(Keels_Drawing_Canvas* cCanvas, float dx, float dy)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Translate(dx, dy);
}

void Keels_Drawing_CanvasScale(Keels_Drawing_Canvas* cCanvas, float sx, float sy)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Scale(sx, sy);
}

void Keels_Drawing_CanvasSkew(Keels_Drawing_Canvas* cCanvas, float sx, float sy)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Shear(sx, sy);
}

void Keels_Drawing_CanvasClear(Keels_Drawing_Canvas* cCanvas, uint32_t color)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->Clear(color);
}

int32_t Keels_Drawing_CanvasGetWidth(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return 0;
    }
    return canvas->GetWidth();
}

int32_t Keels_Drawing_CanvasGetHeight(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return 0;
    }
    return canvas->GetHeight();
}

void Keels_Drawing_CanvasGetLocalClipBounds(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Rect* cRect)
{
    if (cRect == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Drawing::Rect rect = canvas->GetLocalClipBounds();
    Drawing::Rect* outRect = reinterpret_cast<Drawing::Rect*>(cRect);
    *outRect = rect;
}

void Keels_Drawing_CanvasGetTotalMatrix(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Matrix* cMatrix)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cMatrix == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    Matrix matrix = canvas->GetTotalMatrix();
    Matrix* outMatrix = reinterpret_cast<Matrix*>(cMatrix);
    *outMatrix = matrix;
}

void Keels_Drawing_CanvasConcatMatrix(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Matrix* cMatrix)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cMatrix == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->ConcatMatrix(*reinterpret_cast<Matrix*>(cMatrix));
}

void Keels_Drawing_CanvasDrawShadow(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Path* cPath, Keels_Drawing_Point3D cPlaneParams,
    Keels_Drawing_Point3D cDevLightPos, float lightRadius, uint32_t ambientColor, uint32_t spotColor,
    Keels_Drawing_CanvasShadowFlags flag)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cPath == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    if (flag < SHADOW_FLAGS_NONE || flag > SHADOW_FLAGS_ALL) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
        return;
    }
    canvas->DrawShadow(*reinterpret_cast<Path*>(cPath), CastToPoint3(cPlaneParams), CastToPoint3(cDevLightPos),
        lightRadius, Color(ambientColor), Color(spotColor), static_cast<ShadowFlags>(flag));
}

void Keels_Drawing_CanvasSetMatrix(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Matrix* matrix)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || matrix == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->SetMatrix(CastToMatrix(*matrix));
}

void Keels_Drawing_CanvasResetMatrix(Keels_Drawing_Canvas* cCanvas)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->ResetMatrix();
}

void Keels_Drawing_CanvasDrawImageRectWithSrc(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Image* cImage,
    const Keels_Drawing_Rect* src, const Keels_Drawing_Rect* dst, const Keels_Drawing_SamplingOptions* cSampling,
    Keels_Drawing_SrcRectConstraint constraint)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cImage == nullptr || src == nullptr || dst == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawImageRect(CastToImage(*cImage), CastToRect(*src), CastToRect(*dst),
        cSampling ? CastToSamplingOptions(*cSampling) : Drawing::SamplingOptions(),
        static_cast<SrcRectConstraint>(constraint));
}

void Keels_Drawing_CanvasDrawImageRect(
    Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Image* cImage, Keels_Drawing_Rect* dst, Keels_Drawing_SamplingOptions* cSampling)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cImage == nullptr || dst == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return;
    }
    canvas->DrawImageRect(CastToImage(*cImage), CastToRect(*dst),
        cSampling ? CastToSamplingOptions(*cSampling) : Drawing::SamplingOptions());
}

bool Keels_Drawing_CanvasReadPixels(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Image_Info* cImageInfo, void* dstPixels,
    uint32_t dstRowBytes, int32_t srcX, int32_t srcY)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cImageInfo == nullptr || dstPixels == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return false;
    }
    ImageInfo imageInfo(cImageInfo->width, cImageInfo->height, static_cast<ColorType>(cImageInfo->colorType),
        static_cast<AlphaType>(cImageInfo->alphaType));
    return canvas->ReadPixels(imageInfo, dstPixels, dstRowBytes, srcX, srcY);
}

bool Keels_Drawing_CanvasReadPixelsToBitmap(
    Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Bitmap* cBitmap, int32_t srcX, int32_t srcY)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr || cBitmap == nullptr) {
        g_drawingErrorCode = KEELS_DRAWING_ERROR_INVALID_PARAMETER;
        return false;
    }
    return canvas->ReadPixels(CastToBitmap(*cBitmap), srcX, srcY);
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasIsClipEmpty(Keels_Drawing_Canvas* cCanvas, bool* isClipEmpty)
{
    if (isClipEmpty == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    *isClipEmpty = canvas->IsClipEmpty();
    return KEELS_DRAWING_SUCCESS;
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasGetImageInfo(Keels_Drawing_Canvas* cCanvas, Keels_Drawing_Image_Info* cImageInfo)
{
    if (cCanvas == nullptr || cImageInfo == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    ImageInfo imageInfo = CastToCanvas(cCanvas)->GetImageInfo();

    cImageInfo->width = imageInfo.GetWidth();
    cImageInfo->height = imageInfo.GetHeight();
    cImageInfo->colorType = static_cast<Keels_Drawing_ColorFormat>(imageInfo.GetColorType());
    cImageInfo->alphaType = static_cast<Keels_Drawing_AlphaFormat>(imageInfo.GetAlphaType());
    return KEELS_DRAWING_SUCCESS;
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasClipRegion(
    Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Region* cRegion, Keels_Drawing_CanvasClipOp op)
{
    if (cRegion == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }

    if (op < DIFFERENCE || op > INTERSECT) {
        return KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
    }
    canvas->ClipRegion(CastToRegion(*cRegion), static_cast<ClipOp>(op));
    return KEELS_DRAWING_SUCCESS;
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasDrawPoint(Keels_Drawing_Canvas* cCanvas, const Keels_Drawing_Point2D* cPoint)
{
    if (cPoint == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }
    canvas->DrawPoint(CastToPoint(*cPoint));
    return KEELS_DRAWING_SUCCESS;
}

Keels_Drawing_ErrorCode Keels_Drawing_CanvasDrawColor(
    Keels_Drawing_Canvas* cCanvas, uint32_t color, Keels_Drawing_BlendMode cBlendMode)
{
    Canvas* canvas = CastToCanvas(cCanvas);
    if (canvas == nullptr) {
        return KEELS_DRAWING_ERROR_INVALID_PARAMETER;
    }

    if (cBlendMode < KEELS_BLEND_MODE_CLEAR || cBlendMode > KEELS_BLEND_MODE_LUMINOSITY) {
        return KEELS_DRAWING_ERROR_PARAMETER_OUT_OF_RANGE;
    }

    canvas->DrawColor(color, static_cast<BlendMode>(cBlendMode));
    return KEELS_DRAWING_SUCCESS;
}